跳到主要内容

SpringBoot 整合 Redis

配置环境

参考资料 Spring Boot (五): Redis缓存使用姿势盘点

添加 Maven 依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>

<!-- 把实体类转换成 JSON 的工具 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.2</version>
</dependency>

<!--
需要配置连接池,所以要引入这个包
==============科普=================
druid 和 这个 commons-pool2 的关系
commons-pool2 是一个对象池工具,而 druid 是更高一级的封装即数据库连接池
相当于 一些数据库连接池是通过 commons-pool2 实现的

例如:如下使用commons-pool2 实现数据库连接池
https://blog.csdn.net/a19881029/article/details/81436193
-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.8.0</version>
</dependency>

<!-- 引入 redis 的 Session 管理 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>2.3.0.RELEASE</version>
</dependency>

配置 yml

注意:因为在 Spring Boot 2.x 后底层不再是使用 Jedis ,而是换成了 Lettuce 所以只需配置 Lettuce

spring:
redis:
host: 127.0.0.1
port: 6379
# 连接超时时间(ms)
timeout: 10000
# Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
database: 0
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制) 默认 8
max-active: 100
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
max-wait: -1
# 连接池中的最大空闲连接 默认 8
max-idle: 8
# 连接池中的最小空闲连接 默认 0
min-idle: 0
# 注意:别忘了设置缓存用的是 redis
cache:
type: redis

Redis 配置示例

参考资料 Spring Boot 2.X 实战--Spring Boot 整合 Redis

配置 RedisTemplate

除了使用注解,我们还可以使用 Redis 模板。 Spring boot集成 Redis 客户端 jedis。封装Redis 连接池,以及操作模板。

@Autowired
private StringRedisTemplate stringRedisTemplate;//操作key-value都是字符串

@Autowired
private RedisTemplate redisTemplate;//操作key-value都是对象

/**
* Redis常见的五大数据类型:
* stringRedisTemplate.opsForValue();[String(字符串)]
* stringRedisTemplate.opsForList();[List(列表)]
* stringRedisTemplate.opsForSet();[Set(集合)]
* stringRedisTemplate.opsForHash();[Hash(散列)]
* stringRedisTemplate.opsForZSet();[ZSet(有序集合)]
*/
public void test(){
stringRedisTemplate.opsForValue().append("msg","hello");
}

详细配置

/**
* 配置 RedisTemplate
*
* @author alsritter
* @version 1.0
**/
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
// @EnableRedisRepositories
public class RedisRepositoryConfig {

/**
* 自定义 JSON Template
*
* @param connectionFactory redisConnectionFactory
* @return RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> jsonRedisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);

StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);

// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 Redis 的 Value
Jackson2JsonRedisSerializer<?> serializer = new Jackson2JsonRedisSerializer<>(Object.class);

// 自定义的一个映射配置(ObjectMapper 提供了读取和写入 JSON 的功能)
ObjectMapper mapper = new ObjectMapper();
// 设置未找到该属性的值时不抛出错误
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 默认设置属性有非空这个注释
mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

// 该方法是指定序列化输入的类型,就是将数据库里的数据按照一定类型存储到 redis 缓存中。
// mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); // 新版本要使用这个方法代替

// 为什么要指定序列化输入类型?
// 1、没有指定序列化输入类型的情况:
// 如果注释掉 enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL),那存储到 redis 里的数据将是没有类型的纯 json,
// 我们调用 redis API 获取到数据后,java 解析将是一个 LinkHashMap 类型的 key-value 的数据结构,我们需要使用的话就要自行解析,这样增加了编程的复杂度。
// [{"id":72,"uuid":"c4d7fc52-4096-4c79-81ef-32cb1b87fd28","type":2}]

// 2、指定序列化输入类型的情况:
// 指定 enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL) 的话,存储到 Redis 里的数据将是有类型的 json 数据,例如:
// ["java.util.ArrayList",[{"@class":"com.model.app","id":72,"uuid":"c4d7fc52-4096-4c79-81ef-32cb1b87fd28","type":2}]]
// 这样 java 获取到数据后,将会将数据自动转化为 java.util.ArrayList 和 com.model.app,方便直接使用。

serializer.setObjectMapper(mapper);

// 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key 值
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashValueSerializer(serializer);
redisTemplate.afterPropertiesSet();

return redisTemplate;
}

/**
* redis模板,主要用于存放序列化对象
*
* @param redisConnectionFactory redisConnectionFactory
* @return redis模板
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
RedisSerializer<String> stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setConnectionFactory(redisConnectionFactory);
return template;
}

/**
* 这个 @ConditionalOnMissingBean 注解作用在 @bean 定义上,
* 它的作用就是在容器加载它作用的 bean 时,检查容器中是否存在目标类型(ConditionalOnMissingBean 注解的 value 值)的 bean 了,
* 如果存在,则跳过原始 bean 的 BeanDefinition 加载动作。
*/
@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}

实体类需要继承 Serializable 接口

@Data@ToString
public class User implements Serializable {
private Long id;
private String userName;
private String password;
private String name;
private Integer age;
private Integer sex;
private String note;
private Date birthday;
private Date created;
private Date updated;
}

使用例

参考资料 Spring Boot 2.X 实战--Spring Boot 整合 Redis

@RestController
@CacheConfig(cacheNames = "users")
public class RedisController {
@Resource
StringRedisTemplate stringTemplate;
@Resource
RedisTemplate<String, User> redisTemplate;

@RequestMapping("/setString")
public String setString(@RequestParam(value = "key") String key,
@RequestParam(value = "value") String value) {
stringTemplate.opsForValue().set(key, value);
return "ok";
}

@RequestMapping("/getString")
public String getString(@RequestParam(value = "key") String key) {
return stringTemplate.opsForValue().get(key);
}
// User 类set、get 和时间限制
@RequestMapping(value = "/setUser")
public String setUser(@RequestBody User user) {
// 1 分钟后过期
redisTemplate.opsForValue().set(user.getUsername(), user, Duration.ofMinutes(1));
return "ok";
}

@RequestMapping("/getUser")
public User getUser(@RequestParam(value = "key") String key) {
return redisTemplate.opsForValue().get(key);
}

@RequestMapping("/delUser")
public User delUser(@RequestParam(value = "key") String key) {
User user = redisTemplate.opsForValue().get(key);
// 删除
redisTemplate.delete(key);
return user;
}
}

操作 HashMap

新增 hashMap 值。

redisTemplate.opsForHash().put("hashValue","map1","map1-1");  
redisTemplate.opsForHash().put("hashValue","map2","map2-2");

获取指定变量中的 hashMap 值。

List<Object> hashList = redisTemplate.opsForHash().values("hashValue");  
System.out.println("通过 values(H key) 方法获取变量中的 hashMap 值:" + hashList);

可以编写一个 HashManger 方便管理 Hash 数据

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
* @author alsritter
* @version 1.0
**/
@Slf4j
@Service("redisHashManager")
@Setter(onMethod_ = {@Autowired})
public class RedisHashManager<T> {
private RedisTemplate<String, Object> jsonRedisTemplate;

/**
* 写去缓存 hash
*
* @param key key
* @param hashKey hashKey
* @param value value
* @param expireTime 过期时间
* @return 结果
*/
public boolean put(final String key, String hashKey, T value, Long expireTime) {
boolean result = false;
try {
jsonRedisTemplate.opsForHash().put(key, hashKey, value);
jsonRedisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
result = true;
} catch (Exception e) {
log.error("写入redis失败", e);
}
return result;
}


/**
* 写去缓存 hash
*
* @param key key
* @param hashKey hashKey
* @param value value
* @return 结果
*/
public boolean put(final String key, String hashKey, T value) {
boolean result = false;
try {
jsonRedisTemplate.opsForHash().put(key, hashKey, value);
result = true;
} catch (Exception e) {
log.error("写入redis失败", e);
}
return result;
}

/**
* 读取缓存 Hash
*
* @param key key
* @param hashKey hashKey
* @return object
*/
public T get(final String key, String hashKey) {
return (T) jsonRedisTemplate.opsForHash().get(key, hashKey);
}

/**
* 删除缓存 Hash
*
* @param key key
* @param hashKey hashKey
* @return object
*/
public Long delete(final String key, String hashKey) {
return jsonRedisTemplate.opsForHash().delete(key, hashKey);
}

/**
* 删除缓存Hash
*
* @param key key
* @return object
*/
public Boolean delete(final String key) {
return jsonRedisTemplate.delete(key);
}

/**
* 删除缓存Hash
*
* @param key key
*/
public void deleteAll(final String key) {
HashOperations<String, Object, T> operations = jsonRedisTemplate.opsForHash();
Set<Object> hashKeySet = operations.keys(key);
for (Object object : hashKeySet) {
operations.delete(key, String.valueOf(object));
}
}

/**
* redis
*
* @param jsonRedisTemplate jsonRedisTemplate
*/
public void setJsonRedisTemplate(RedisTemplate<String, Object> jsonRedisTemplate) {
this.jsonRedisTemplate = jsonRedisTemplate;
}
}

RedisTemplate 配置模板

直接粘贴可用

Maven

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

yml

spring:
redis:
database: 0
port: 6379
host: localhost
password:

RedisTemplate 的 Bean

@Configuration
@EnableRedisRepositories
public class RedisRepositoryConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}

这样就能直接使用了

@Autowired
RedisTemplate<String, Object> redisTemplate;

数据库缓存示例 ⭐

参考资料 Spring Boot 2.X 实战--Spring Boot 整合 Redis 参考资料 Spring Boot (五): Redis缓存使用姿势盘点

完成 Redis 基础配置之后,就可以使用 Redis 对数据进行缓存了

就只需要这一个依赖!不需要 spring-boot-starter-cache

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

当导入这一个依赖时,SpringBoot 的 CacheManager 就会使用 RedisCache。

缓存常用的三个注解

@Cacheable(key = "#username") 表示将 username 作为 key,将结果写入 Redis 缓存。如果缓存中已经有此 key 值,则直接返回,而不会执行 SQL 查询,缓存中没有数据的情况下,才会执行 SQL 查询。(即先看缓存有没有,有就直接返回,没有就查询数据库并把数据存到缓存区里)

@CachePut(key = "#user.username"), 跟 @Cacheable 类似,起作用是在 myUpdate(),执行结束后才会写入或者更新缓存中的内容。

@CacheEvict(key = "#username") 根据 key 值删除缓存中的内容。如果指定 allEntries = true 则会删除 @CacheConfig(cacheNames = "users") cacheNames 下的所有缓存。

Mapper 层

public interface UserMapper {
@Select("Select username, age From user Where username=#{username}")
User selectByUsername(String username);

@Update("Update user Set age=#{age} Where username=#{username}")
void update(User user);

@Delete("Delete From user where username=#{username}")
void delete(String username);
}

Controller 层

@RequestMapping("/deleteAllCache")
@CacheEvict(allEntries = true)
public String deleteAllCache() {
// 删除所有缓存
return "OK";
}

@RequestMapping("/mySelect")
@Cacheable(key = "#username")
public User mySelect(@RequestParam(value = "username") String username) {
return userMapper.selectByUsername(username);
}

@RequestMapping("/myUpdate")
@CachePut(key = "#user.username")
public User myUpdate(@RequestBody User user) {
userMapper.update(user);
return userMapper.selectByUsername(user.getUsername());
}

@RequestMapping("/myDelete")
@CacheEvict(key = "#username")
public User myDelete(@RequestParam(value = "username") String username) {
userMapper.delete(username);
return userMapper.selectByUsername(username);
}

运行项目,访问 /mySelect?username=boot ,第一次访问或者缓存过期后访问,终端会打印 SQL 语句,查询 SQL,缓存有效期内访问,直接返回缓存中的内容。而不会去查数据库。

参考资料 解决Springboot使用Redis反序列化遇到的类型转换异常 注意!!!这里有个坑:如果使用了热部署工具会导致 Redis 反序列化出问题

<!-- 就是这个 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>

当对象被序列化到缓存里时,当前应用的类加载器是C1,当改变了一些代码或者配置文件的时候,DevTools 工具将会自动重新启动这个容器,并且创建一个新的类加载器 C2. 这时候调用这个具有缓存的方法时,缓存管理将会从缓存里找到该条缓存记录并进行反序列化操作。 如果缓存库不考虑上下文的话,也就是没注意到类加载器的变化时,该对象将会有错误的类加载器

最后需要在启动器类加上 @EnableCaching 表示启用缓存

@SpringBootApplication
@MapperScan("com.alsritter.mapper")
@EnableCaching
public class StudyApplication {

public static void main(String[] args) {
SpringApplication.run(StudyApplication.class, args);
}

}

测试用

可以在 Service 层加上个 log 判断一下是访问的缓存还是数据库

public User queryById(Long id){
log.info("是否查询了数据库?");
return userMapper.queryById(id);
}

在 Controller 层启用缓存

@GetMapping("/temp3")
@Cacheable(value = "user", key = "#id")
public User queryById(Long id) {
return userService.queryById(id);
}

StringRedisTemplate 和 RedisTemplate

StringRedisTemplate 和 RedisTemplate 的区别及使用方法

1、StringRedisTemplate 继承自 RedisTemplate。

2、两者的数据是不共通的;也就是说 StringRedisTemplate 只能管理 StringRedisTemplate 里面的数据,RedisTemplate 只能管理 RedisTemplate 中的数据。

3、SDR 默认采用的序列化策略有两种,一种是 String 的序列化策略,一种是 JDK 的序列化策略。

StringRedisTemplate 默认采用的是 String 的序列化策略,保存的 key 和 value 都是采用此策略序列化保存的。而 RedisTemplate 默认采用的是 JDK 的序列化策略,保存的 key 和 value 都是采用此策略序列化保存的。

RedisTemplate 使用的序列类在在操作数据的时候,比如说存入数据会将数据先序列化成字节数组然后在存入 Redis 数据库,这个时候打开 Redis 查看的时候,会看到数据不是以可读的形式展现的,而是以字节数组显示,类似下面

当然从 Redis 获取数据的时候也会默认将数据当做字节数组转化,这样就会导致一个问题,当需要获取的数据不是以字节数组存在 Redis 当中而是正常的可读的字符串的时候,比如说下面这种形式的数据

RedisTemplate 就无法获取导数据,这个时候获取到的值就是 NULL。这个时候 StringRedisTempate 就派上了用场。当 Redis 当中的数据值是以可读的形式显示出来的时候,只能使用 StringRedisTemplate 才能获取到里面的数据。

所以当你使用 RedisTemplate 获取不到数据的时候请检查一下是不是 Redis 里面的数据是可读形式而非字节数组